5. Menus

5. Menus

5.1. Menús: Elemento a elemento

Existen varias formas de añadir un menú a una ventana, veremos cada una de ellas por separado.

INSERCIÓN ELEMENTO A ELEMENTO

Es el sistema más rudimentario, pero en ocasiones puede ser muy útil. Este sistema ilustra mucho mejor la estructura de los menús.

#define CM_PRUEBA 100  // constante ID del menú PRUEBA
#define CM_SALIR  101  // constante ID del menú SALIR
(...)
void InsertarMenu(HWND);  // prototipo
#define CM_PRUEBA 100  // constante ID del menú PRUEBA
#define CM_SALIR  101  // constante ID del menú SALIR
(...)
void InsertarMenu(HWND);  // prototipo

Al final del programa:

(...)
void InsertarMenu(HWND hWnd)
{ 
    HMENU hBarraMenus; 
    HMENU hMenuPopup;

    hBarraMenus = CreateMenu(); // Crea el manipulador de la barra de menús 
    hMenuPopup = CreateMenu(); // Crea el manipulador del primer menú pop-up 
    
    // añade los elementos tipo cadena y/o separador al menú pop-up
    AppendMenu(hMenuPopup, MF_STRING, CM_PRUEBA, “&Prueba”); // 1er ítem 
    AppendMenu(hMenuPopup, MF_SEPARATOR, 0, NULL); // 2º ítem (separador) 
    AppendMenu(hMenuPopup, MF_STRING, CM_SALIR, “&Salir”); // 3er ítem
    
    // Inserta el menú pop-up en la barra de menús
    AppendMenu(hBarraMenus, MF_STRING | MF_POPUP, (UINT)hMenuPopup, “&Principal”); 
    
    // Inserta la barra de menús en la ventana 
    SetMenu (hWnd, hBarraMenus); 
}
(...)
void InsertarMenu(HWND hWnd)
{ 
    HMENU hBarraMenus; 
    HMENU hMenuPopup;

    hBarraMenus = CreateMenu(); // Crea el manipulador de la barra de menús 
    hMenuPopup = CreateMenu(); // Crea el manipulador del primer menú pop-up 
    
    // añade los elementos tipo cadena y/o separador al menú pop-up
    AppendMenu(hMenuPopup, MF_STRING, CM_PRUEBA, “&Prueba”); // 1er ítem 
    AppendMenu(hMenuPopup, MF_SEPARATOR, 0, NULL); // 2º ítem (separador) 
    AppendMenu(hMenuPopup, MF_STRING, CM_SALIR, “&Salir”); // 3er ítem
    
    // Inserta el menú pop-up en la barra de menús
    AppendMenu(hBarraMenus, MF_STRING | MF_POPUP, (UINT)hMenuPopup, “&Principal”); 
    
    // Inserta la barra de menús en la ventana 
    SetMenu (hWnd, hBarraMenus); 
}

Y por último, sólo nos queda llamar a nuestra función, insertaremos ésta llamada justo antes de visualizar la ventana.

InsertarMenu(hWnd); ShowWindow(hWnd, SW_SHOWDEFAULT);
InsertarMenu(hWnd); ShowWindow(hWnd, SW_SHOWDEFAULT);

Explicación

HMENU es un tipo de manipulador especial para menús. Necesitamos dos uno para el menú horizontal (barra de menús) y otro para el menú desplegable.

La función CreateMenu crea un menú vacío.
La función AppendMenu añade elementos uno a uno indicando, el menú donde hacer la inserción y las opciones del nuevo elemento: MF_STRING, indica que se trata de un ítem de tipo texto, MF_SEPARATOR, es un ítem separador y MF_POPUP, indica que se trata de un menú que desplegará un nuevo menú pop-up. El tercer parámetro puede ser un identificador de comando, este identificador se usará para comunicar a la aplicación si el usuario selecionó un determinado ítem; o puede ser un manipulador de menú, si el ítem tiene el flag MF_POPUP, en este caso hay que hacer un casting a (UINT); o también puede ser cero, si se trata de un separador. El último parámetro es el texto del ítem, cuando se ha especificado el flag MF_STRING, más adelante veremos que los ítems pueden ser también bitmaps. Normalmente se trata de una cadena de texto con el & para indicar la tecla de atajo.

Por último SetMenu asigna un menú a una ventana determinada. El primer parámetro es el manipulador de la ventana, y el segundo el del menú.

Captura y respuesta de mensajes

Las acciones sobre los menús se reciben como mensajes WM_COMMAND
Ese mensaje tiene tres argumentos de los cuales sólo nos interesa el segundo

wNotifyCode = HIWORD(wParam); // código de notificación
wID = LOWORD(wParam);         // identificador de ítem, control, o acelerador 
hwndCtl = (HWND) lParam;      // manipulador de control
wNotifyCode = HIWORD(wParam); // código de notificación
wID = LOWORD(wParam);         // identificador de ítem, control, o acelerador 
hwndCtl = (HWND) lParam;      // manipulador de control

Para sacar el identificador usaremos la macro LOWORD. Cambiamos el código de la función de procedimiento de ventana:

/*  Esta función es invocada por la función DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
    switch (mensaje) // manipulador de mensaje
    {
        case WM_COMMAND:  // menús, un control envía un mensaje de notificación a su ventana padre, o una tecla atajo
           switch(LOWORD(wParam)) // saca el low word (identificador de ítem, control, o acelerador) del mensaje
           {
              case CM_PRUEBA:
                 MessageBox(hwnd, "Comando: Prueba", "Mensaje de menú", MB_OK);
              break;

              case CM_SALIR:
                 MessageBox(hwnd, "Comando: Salir", "Mensaje de menú", MB_OK);

                 PostQuitMessage(0); // envía el mensaje WM_QUIT a la cola de mensajes
              break;
           }
        break;


        case WM_DESTROY:
            PostQuitMessage (0);  // envía el mensaje WM_QUIT a la cola de mensajes
        break;


        default: // Mensajes que no queremos manejar y lo hará el sistema operativo
            return DefWindowProc (hwnd, mensaje, wParam, lParam);
    }

    return 0;
}
/*  Esta función es invocada por la función DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT mensaje, WPARAM wParam, LPARAM lParam)
{
    switch (mensaje) // manipulador de mensaje
    {
        case WM_COMMAND:  // menús, un control envía un mensaje de notificación a su ventana padre, o una tecla atajo
           switch(LOWORD(wParam)) // saca el low word (identificador de ítem, control, o acelerador) del mensaje
           {
              case CM_PRUEBA:
                 MessageBox(hwnd, "Comando: Prueba", "Mensaje de menú", MB_OK);
              break;

              case CM_SALIR:
                 MessageBox(hwnd, "Comando: Salir", "Mensaje de menú", MB_OK);

                 PostQuitMessage(0); // envía el mensaje WM_QUIT a la cola de mensajes
              break;
           }
        break;


        case WM_DESTROY:
            PostQuitMessage (0);  // envía el mensaje WM_QUIT a la cola de mensajes
        break;


        default: // Mensajes que no queremos manejar y lo hará el sistema operativo
            return DefWindowProc (hwnd, mensaje, wParam, lParam);
    }

    return 0;
}

5.2. Menús: Ficheros de recursos

La forma más habitual de hacer menús: crear un fichero de recursos en el que se declaran los menús.

Archivo de identificadores “ids.h”

Creamos un archivo de cabecera “ids.h” y añadimos las constantes de los IDs de los menús:

#define CM_PRUEBA 100 
#define CM_SALIR  101
#define CM_PRUEBA 100 
#define CM_SALIR  101

Siempre use identificadores para los ítems de los menús, nunca valores numéricos.

Incluimos ese archivo en el “win003.c” con #include “ids.h” justo después de incluir la cabecera de windows:

#include <windows.h>
#include "ids.h"
#include <windows.h>
#include "ids.h"

Archivo de recursos “win003.rc”

Creamos el archivo rc y lo añadimos al proyecto. La primera línea ha de incluir la cabecera de los identificadores

#include "ids.h"

BarraMenus MENU
BEGIN
   POPUP "&Principal"
      BEGIN
         MENUITEM "&Prueba", CM_PRUEBA
         MENUITEM SEPARATOR
         MENUITEM "&Salir", CM_SALIR
      END
END
#include "ids.h"

BarraMenus MENU
BEGIN
   POPUP "&Principal"
      BEGIN
         MENUITEM "&Prueba", CM_PRUEBA
         MENUITEM SEPARATOR
         MENUITEM "&Salir", CM_SALIR
      END
END

Este ejemplo crea una barra de menú con una columna “Principal”, con dos opciones: “Prueba” y “Salir”, y con un separador entre ellas.

Definimos el menú mediante una cadena identificadora, sin comillas, seguida de la palabra MENU. Entre las palabras BEGIN y END podemos incluir items, separadores u otras columnas. Para incluir columas usamos una sentencia del tipo POPUP seguida de la cadena que se mostrará como texto en el menú. Cada POPUP se comporta del mismo modo que un MENU.

Los ítems se crean usado la palabra MENUITEM seguida de la cadena que se mostrará en el menú, una coma, y el comando asignado a ese ítem, como una macro definida.

Los separadores se crean usando MENUITEM SEPARATOR.

CARGA DEL ARCHIVO DE RECURSOS

Cargarlo antes de mostrar la ventana

SetMenu(hWnd, LoadMenu(hInstance, "BarraMenus"));
ShowWindow(hWnd);
SetMenu(hWnd, LoadMenu(hInstance, "BarraMenus"));
ShowWindow(hWnd);

Asignarlo como menú por defecto de la clase

WNDCLASSEX wincl;
   (...)
wincl.lpszMenuName = "BarraMenus"; 
WNDCLASSEX wincl;
   (...)
wincl.lpszMenuName = "BarraMenus"; 

Asignarlo al crear la ventana en CreateWindowEx

hWnd = CreateWindowEx(
          0,
          "NUESTRA_CLASE",
          "Ejemplo 003",
          WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT,
          CW_USEDEFAULT,
          544,
          375,
          HWND_DESKTOP,
          LoadMenu(hInstance, "BarraMenus"), /* Carga y asignación del menú */
          hInstance,
          NULL
    );
hWnd = CreateWindowEx(
          0,
          "NUESTRA_CLASE",
          "Ejemplo 003",
          WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT,
          CW_USEDEFAULT,
          544,
          375,
          HWND_DESKTOP,
          LoadMenu(hInstance, "BarraMenus"), /* Carga y asignación del menú */
          hInstance,
          NULL
    );